package worldModel;

import java.awt.Color;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JLabel;
import javax.swing.JOptionPane;

import view.MasterView;
import view.TradeView;
import view.Views;
import worldModel.Item;

public class TradeModel {

    private Navigator nav;
    private List<Item> navigatorList;
    private List<Item> givingList;
    private List<Item> receivingList;
    private ServerSocket server;
    private Socket client;
    private TradeView view;

    private MasterView master;
    private ReceivingListUpdater rUp;
    private TradeAcceptedListener tradeListen;
    private HasQuitListener quitListen;
    // private ConnectionListener cListen;

    private ObjectOutputStream navOut;
    private ObjectInputStream navIn;

    private ObjectOutputStream outStream;
    private AtomicBoolean connectionOpen;
    private AtomicBoolean connectionHasBeenClosed;
    private AtomicBoolean tradeAccepted;
    private AtomicBoolean otherTradeAccepted;
    private AtomicBoolean otherHasQuit;
    private AtomicBoolean shutdownThreads;
    private ObjectInputStream inStream;

    public TradeModel(TradeView v, MasterView m) throws IOException,
	    ClassNotFoundException {
	master = m;
	navigatorList = new ArrayList<Item>();
	givingList = new ArrayList<Item>();
	receivingList = new ArrayList<Item>();
	view = v;
	connectionOpen = new AtomicBoolean(false);
	connectionHasBeenClosed = new AtomicBoolean(false);
	tradeAccepted = new AtomicBoolean(false);
	otherTradeAccepted = new AtomicBoolean(false);
	otherHasQuit = new AtomicBoolean(false);
	shutdownThreads = new AtomicBoolean(false);
	readNavigator();
	buildNavList();

	// Setup new threads for maintaining content
	rUp = new ReceivingListUpdater();
	tradeListen = new TradeAcceptedListener();
	quitListen = new HasQuitListener();
	// cListen = new ConnectionListener();
    }

    private class HasQuitListener extends Thread {
	public void run() {
	    while (!shutdownThreads.get()) {

		while (connectionOpen.get()) {
		    if (!connectionHasBeenClosed.get()) {
			if (otherHasQuit.get()) {
			    endTheTrade();
			    break;
			}
		    } else {
			break;
		    }
		}
	    }
	}
    }

    private class TradeAcceptedListener extends Thread {
	public void run() {
	    while (!shutdownThreads.get()) {

		while (connectionOpen.get()) {
		    if (!connectionHasBeenClosed.get()) {
			if (tradeAccepted.get() && otherTradeAccepted.get()) {

			    // Add the receivingList to the navigatorList
			    navigatorList.addAll(receivingList);
			    JOptionPane.showMessageDialog(master,
				    "Trade completed!");
			    try {
				writeNavigator();
			    } catch (FileNotFoundException e1) {
				System.out
					.println("Could not find the proper directory to write navigator.dat.");
			    } catch (IOException e1) {
				System.out
					.println("IOException while writing navigator.dat (Trade completed)");
			    }
			    closeConnection();
			    master.changeView(Views.TITLE, null);
			}
		    } else {
			break;
		    }
		}
	    }
	}
    }

    private class ReceivingListUpdater extends Thread {
	public void run() {
	    while (!shutdownThreads.get()) {
		while (connectionOpen.get()) {
		    if (!connectionHasBeenClosed.get()) {
			updateReceiveData();
		    } else {
			break;
		    }
		}
	    }
	}
    }

    public void ToggleTrade() {
	if (connectionOpen.get()) {
	    if (tradeAccepted.get()) {
		tradeAccepted.set(false);
		view.getComponent(7).setBackground(new Color(255, 100, 100));
		try {
		    outStream.writeObject(new Boolean(false));
		} catch (IOException e) {
		    System.out
			    .println("Problem when sending 'false' to the other user!");
		}
	    } else {
		tradeAccepted.set(true);
		view.getComponent(7).setBackground(new Color(100, 255, 100));
		try {
		    outStream.writeObject(new Boolean(true));
		} catch (IOException e) {
		    System.out
			    .println("Problem when sending 'true' to the other user!");
		}
	    }
	}
    }

    public void Quit() {
	if (connectionOpen.get()) {
	    try {
		outStream.writeObject(new String("lol important stuff"));
	    } catch (IOException e) {
		System.out.println("Error while sending a string.");
	    }
	}
    }

    private void writeNavigator() throws FileNotFoundException, IOException {
	navOut = new ObjectOutputStream(new FileOutputStream(new File(
		"navigator.dat")));
	navOut.writeObject(nav);
	navOut.close();
    }

    private void readNavigator() {
	File navFile = new File("navigator.dat");
	try {
	    navIn = new ObjectInputStream(new FileInputStream(navFile));
	    try {
		nav = (Navigator) navIn.readObject();
	    } catch (ClassNotFoundException e) {
		System.out
			.println("ClassNotFoundException while reading navigator.dat");
	    }
	    navIn.close();
	} catch (Exception e) {
	    System.out
		    .println("Could not find navigator.dat. Creating a new one!");
	    e.printStackTrace();
	    nav = new Navigator();
	    nav.addItem(new SmallHeal());
	    nav.addItem(new MediumHeal());
	    nav.addItem(new LargeHeal());
	    try {
		writeNavigator();
	    } catch (FileNotFoundException e1) {
		System.out
			.println("Could not find the proper directory to write navigator.dat.");
	    } catch (IOException e1) {
		System.out
			.println("IOException while writing navigator.dat (writing new navigator)");
		// e1.printStackTrace();
	    }
	}
    }

    private void buildNavList() {
	navigatorList = nav.getInventory();
    }

    // 6,7,8,9 = JButtons
    public void connectToServer() throws IOException {
	String ip = "";
	ip = JOptionPane
		.showInputDialog("What is the IP of the user you would like to trade with?");
	view.getComponent(8).setEnabled(false);
	view.getComponent(9).setEnabled(false);
	try {
	    System.out.println("Establishing a connection...");
	    client = new Socket(ip, 4000);
	    System.out.println("Connected!");
	    // Start running the threads to keep all data updated between the
	    // clients
	    rUp.start();
	    tradeListen.start();
	    quitListen.start();
	    inStream = new ObjectInputStream(client.getInputStream());
	    outStream = new ObjectOutputStream(client.getOutputStream());
	    connectionOpen.set(true);
	    // Enable the inv list so the user can add items to the trade
	    view.enableInvList();
	} catch (ConnectException e) {
	    System.out.println("Connection timed out!");
	    view.getComponent(8).setEnabled(true);
	    view.getComponent(9).setEnabled(true);
	} catch (IOException e) {
	    System.out
		    .println("Something went wrong while attempting to connect. Malformed IP?");
	    view.getComponent(8).setEnabled(true);
	    view.getComponent(9).setEnabled(true);
	}
    }

    // 6,7,8,9 = JButtons
    // The server times out after 7 seconds if a client does not connect.
    public void hostServer() throws IOException {
	server = new ServerSocket(4000);
	// Set 30 seconds timeout
	server.setSoTimeout(30000);
	Socket client;
	view.getComponent(8).setEnabled(false);
	try {
	    client = server.accept();
	    System.out.println("Client found.");
	    // Start running the threads to keep all data updated between the
	    // clients
	    // cListen.start();
	    rUp.start();
	    tradeListen.start();
	    quitListen.start();
	    outStream = new ObjectOutputStream(client.getOutputStream());
	    inStream = new ObjectInputStream(client.getInputStream());
	    view.getComponent(9).setEnabled(false);
	    // Now let the client setup a ServerSocket so it can communicate
	    // with the server.

	    // Enable the inv list so the user can add items to the trade
	    view.enableInvList();
	    connectionOpen.set(true);
	} catch (SocketTimeoutException e) {
	    System.out.println("Could not find a client.");
	    view.getComponent(8).setEnabled(true);
	    server.close();
	}
    }

    public List<Item> getNavigatorList() {
	return navigatorList;
    }

    public List<Item> getGivingList() {
	return givingList;
    }

    public List<Item> getReceivingList() {
	return receivingList;
    }

    public void updateGivingData() {
	sendData(getGivingList());
    }

    private void sendData(List<Item> givingList2) {
	try {
	    outStream.reset();
	    outStream.writeObject(givingList);
	} catch (IOException e) {
	    System.out.println("An error occured while sending data.");
	}
    }

    public void closeConnection() {
	if (client != null) {
	    try {
		client.close();
		connectionHasBeenClosed.set(true);
		connectionOpen.set(false);
		shutdownThreads.set(true);
	    } catch (IOException e) {
		System.out.println("Error while closing the client socket.");
	    }
	}
	if (server != null) {
	    try {
		server.close();
		connectionHasBeenClosed.set(true);
		connectionOpen.set(false);
		shutdownThreads.set(true);
	    } catch (IOException e) {
		System.out.println("Error while closing the server socket.");
	    }
	}
    }

    public void updateReceiveData() {
	receiveData();
    }

    // Lists = 3,4,5
    @SuppressWarnings("unchecked")
    private void receiveData() {
	try {
	    // inStream.reset();
	    Object temp = inStream.readObject();
	    if (temp instanceof List) {
		receivingList = (List<Item>) temp;
		/*
		 * If no exception is thrown, then the other person has updated
		 * the trade. Thus, we must uncheck the Trade button so the
		 * other person doesn't try to scam the user out of his/her
		 * items.
		 */
		tradeAccepted.set(false);
		view.getComponent(7).setBackground(new Color(255, 100, 100));
		view.repaintLists();
	    } else if (temp instanceof String) {
		otherHasQuit.set(true);
	    } else {
		otherTradeAccepted.set((Boolean) temp);
		if (otherTradeAccepted.get())
		    ((JLabel) view.getComponent(10))
			    .setText("                  The other user is ready to trade!");
		else
		    ((JLabel) view.getComponent(10))
			    .setText("          The other user is not ready to trade...");
	    }
	} catch (NullPointerException e) {
	    // Do nothing. It just means the other party hasn't sent a new list
	    // yet.
	} catch (Exception e) {
	    System.out
		    .println("Something went wrong when receiving data. Client disconnect?");
	    if (!otherHasQuit.get()
		    && !(tradeAccepted.get() && otherTradeAccepted.get()))
		endTheTrade();
	}
    }

    private void endTheTrade() {
	closeConnection();
	JOptionPane.showMessageDialog(master, "The trade has been cancelled!");
	master.changeView(Views.TITLE, null);
    }
}